實作串接時會用到的CRUD。
-src
|-app
|-api
|-file
|-base.proto
|-customer.proto
...
|-store.proto
|-web
|-base_pb.d.ts
|-base_pb.js
|-customer_pb.d.ts
|-customer_pb.js
...
|-store_pb.d.ts
|-store_pb.js
|-StoreServiceClientPb.ts
首先
npm install grpc-web -save
npm install google-protobuf -save
把proto檔當成api文件放入專案裡。
把proto轉譯過的ts跟js檔案放入專案裡。
通常跟後端連線以及資料處理會分兩個service:
web.service.ts:處理連線以及把資訊流(stream)轉成Observale
data.service.ts:把自定義的物件轉型成protobuf的物件,
並送去WebService裡得到資訊流(stream)
web.service.ts
:export interface IFilter {
key: string;
val: string | number;
}
export type TFResult =
| DbMessage
| AdminFResult
| CustomerFResult
| LevelFResult
| ProductFResult
| TypeFResult
| OrderFResult
| CarFResult;
export interface IResult {
errorcode: Status;
res: TFResult;
}
@Injectable({
providedIn: "root"
})
export class WebService {
apiUrl = ApiUrl;
store: StoreClient;
constructor() {
this.store = new StoreClient(this.apiUrl, {}, {});
}
/**Base */
//把stream資訊流轉成Observable
getResult(stream: ClientReadableStream<TFResult>)
: Observable<IResult>
{
return Observable.create((obs: Observer<IResult>) => {
//後端傳回值
stream.on("data", data =>
obs.next({
errorcode: null,
res: data
})
);
//後端傳送錯誤訊息
stream.on("status", status =>
obs.next({
errorcode: status,
res: null
})
);
stream.on("error", err => obs.error(err));
stream.on("end", () => obs.complete());
});
}
resData(res: IModel[] | IModel, errocode: number): IData {
return <IData>{
res: res,
errorcode: errocode
};
}
getCustomerList(customerf: CustomerF): Observable<IData> {
let stream = this.store.findCustomer(customerf, {}, null);
let customers = <ICustomer[]>[];
return this.getResult(stream).pipe(
map((data: IResult) => {
if (!!data.res) {
let res = (<CustomerFResult>data.res).toObject();
res.customerList.forEach(item => {
let obj: ICustomer = { ...item };
customers.push(obj);
});
}
return this.resData(customers, 0);
})
);
}
...
}
stream
有兩個函式可以使用:stream.on():資訊流建立起來後,就會一直傳送資料。
stream.cancel():把資訊流取消。
--
stream.on()
有4個模式可以使用:舉個例子:
service Store {
...
// customer 修讀寫
rpc FindCustomer(CustomerF) returns (CustomerFResult) {}
rpc InsertCustomer(Customer) returns (DbMessage) {}
rpc UpdateCustomer(Customer) returns (DbMessage) {}
...
}
後端傳回值的 CustomerFResult
、DbMessage
都會在 data模式 裡回來。
status: 後端自定義的錯誤,包成Status物件,
會從 status模式 傳回來。
error:gRPC底層的錯誤或是連線的錯誤。
end:gRPC關閉。
customer.proto
:message Customer {
int64 id = 1;
string name = 2;
string account = 3;
string password = 4;
int32 status = 5;
int64 level_id = 6;
string level_name = 7;
int64 inserted = 8;
int64 insert_by = 9;
int64 updated = 10;
int64 update_by = 11;
}
message CustomerF {
Limit limit = 1;
string name = 2;
string account = 3;
int32 status = 4;
int64 level_id = 5;
}
message CustomerFResult {
Limit limit = 1;
repeated Customer customer = 2;
}
Customer:customer的物件組成。
CustomerF:對 customerlist 的篩選條件。
CustomerFResult:後端傳回的回應值,
頁數以及 customerlist 陣列。
--
base.proto
:message DbMessage {
int64 inserted_id = 1; // 新增獲得的ID
int64 error_code = 2; // 錯誤代碼
int64 affected_row = 3; // 更新影響的行數
}
message Limit {
int64 length = 1; // 總筆數
int64 page_index = 2; // 第幾頁
int64 page_size = 3; // 一頁幾筆
}
...
--
store.proto
:此為service檔案。
前面寫 rpc 代表是gRPC的method。
service Store {
...
// customer 修讀寫
rpc FindCustomer(CustomerF) returns (CustomerFResult) {}
rpc InsertCustomer(Customer) returns (DbMessage) {}
rpc UpdateCustomer(Customer) returns (DbMessage) {}
...
}
FindCustomer:是搜尋 customerlist。
需要代入 CustomerF 的參數,並會回傳 CustomerFResult 物件。
InsertCustomer:是新增customer。
需要代入 Customer 的參數,並會回傳 DbMessage 物件。
UpdateCustomer:是更新customer。
需要代入 Customer 的參數,並會回傳 DbMessage 物件。
--
app.component.ts
: constructor(private dataService: DataService) {}
ngOnInit() {
this.dataService.getData("customers").subscribe(val => {
console.log(val);
});
}
--
data.service.ts
:@Injectable({
providedIn: "root"
})
export class DataService {
constructor(private webService: WebService) {}
setCustomerFilter(filters?: IFilter[], pageObj?: Limit)
: CustomerF
{
const customerF = new CustomerF();
if (!!filters) {
filters.forEach((item: IFilter) => {
switch (item.key) {
case "name":
customerF.setName(<string>item.val);
break;
case "account":
customerF.setAccount(<string>item.val);
break;
case "status":
customerF.setStatus(<number>item.val);
break;
case "levelId":
customerF.setLevelId(<number>item.val);
break;
}
});
}
if (!!pageObj) {
customerF.setLimit(pageObj);
}
return customerF;
}
getData(
position: string,
id?: number,
filters?: IFilter[],
pageObj?: IPage
)
: Observable<IData>
{
let list: Observable<IData> = null;
switch (position) {
case "customers":
let customerf = this.setCustomerFilter(filters);
list = this.webService.getCustomerList(customerf);
break;
...
}
return list;
}
}
ICustomer 為自定義物件。
CustomerF 為proto檔轉成的ts或js檔裡的物件。
其實proto檔轉成的ts或js檔,要使用裡面的物件,
必須都要寫相對應的setXXX(),所以統一放在這直接做轉接口。
app.component.ts
:let customer: ICustomer = {
name: "cus787",
account: "cus787",
password: "cus787",
levelId: 1,
status: 1,
insertBy: 1,
inserted: new Date().getTime(),
updateBy: 1,
updated: new Date().getTime()
};
this.dataService.insertOne("customers",customer)
.subscribe(val => {
console.log(val);
});
--
data.component.ts
:insertOne(position: string, obj: IModel): Observable<IResult> {
let data: Observable<IResult> = null;
switch (position) {
case "customers":
let customer = this.setCustomer(<ICustomer>obj);
data = this.webService.insertCustomer(customer);
break;
}
return data;
}
updateOne(position: string, obj: IModel): Observable<IResult> {
let data: Observable<IResult> = null;
switch (position) {
case "customers":
let customer = this.setCustomer(<ICustomer>obj);
data = this.webService.updateCustomer(customer);
break;
}
return data;
}
setCustomer(obj: ICustomer): Customer {
const customer = new Customer();
if(!!obj.id){
customer.setId(obj.id);
}
if(!!obj.name){
customer.setName(obj.name);
}
if(!!obj.account){
customer.setAccount(obj.account);
}
if(!!obj.password){
customer.setPassword(obj.password);
}
if(!!obj.levelId){
customer.setLevelId(obj.levelId);
}
if(!!obj.status){
customer.setStatus(obj.status);
}
if(!!obj.insertBy){
customer.setInsertBy(obj.insertBy);
}
if(!!obj.inserted){
customer.setInserted(obj.inserted);
}
if(!!obj.updateBy){
customer.setUpdateBy(obj.updateBy);
}
if(!!obj.updated){
customer.setUpdated(obj.updated);
}
return customer;
}
其實多半做的事情大概就是認真塞要用的物件,
流程一樣是利用proto產生的service,來產生一個資訊流(stream),
再把資訊流轉成Observable使用。
--
web.component.ts
:...
/**Insert */
insertCustomer(customer: Customer): Observable<IResult> {
let stream = this.store.insertCustomer(customer, {}, null);
return this.getResult(stream).pipe(
map((data: IResult) => {
//do something
let m: DbMessage = <DbMessage>data.res;
console.log("insert id", m.getInsertedId());
return data;
})
);
}
/**Update */
updateCustomer(customer: Customer): Observable<IResult> {
let stream = this.store.updateCustomer(customer, {}, null);
return this.getResult(stream).pipe(
map((data: IResult) => {
//do something
let m: DbMessage = <DbMessage>data.res;
console.log("insert id", m.getAffectedRow());
return data;
})
);
}